<?php
class ServiceContainer {
    const INI_PARENT = '.parent';
    const CLASS_NAME = '.class';
    const CTOR_ARGS = '.args';
    
    /**
     *
     * @var array
     */
    private $definitions;
    
    /**
     *
     * @var array
     */
    private $services;
    
    /**
     *
     * @var Config
     */
    private $config;
    
    public function __construct(array $definitions) {
        $this->definitions = &$definitions;
        $this->services = array();
    }
    
    public function getService($name) {
        if (!array_key_exists($name, $this->services)) {
            if (array_key_exists($name, $this->definitions)) {
                $this->services[$name] = $this->instanciateService($name);
                unset($this->definitions[$name]);
            }
            else {
                throw new RuntimeException("No such service: $name");
            }
        }
        
        return $this->services[$name];
    }
    
    private function instanciateService($name) {
        $definition = &$this->definitions[$name];
        
        if (array_key_exists(self::CLASS_NAME, $definition)) {
            $class = new ReflectionClass($definition[self::CLASS_NAME]);
            unset($definition[self::CLASS_NAME]);
            
            $args = array();
            foreach (array_get($definition, self::CTOR_ARGS, array()) as $arg) {
                $args[] = $this->getValue($arg);
            }
            if (!empty($args)) {
                unset($definition[self::CTOR_ARGS]);
            }
            
            $service = $class->newInstance($args);
            
            foreach ($definition as $attr=>$value) {
                $service->{'set'.ucfirst($attr)}($this->getValue($value));
            }
        }
        else {
            throw new RuntimeException("No class specified for service $name");
        }
    }
    
    private function getValue($value) {
        if (empty($value)) {
            return null;
        }
        
        if ($value[0] === '#') {
            return $this->getService(substr($value, 1));
        }
        
        if ($value[0] === '$') {
            return $this->getConfig()->offsetGet(substr($value, 1));
        }
        
        return $value;
    }
    
    public static function createFromIniFile($filename) {
        return new ServiceContainer(inheritIni($filename));
    }
    
    /**
     *
     * @return Config
     */
    public function getConfig() {
        return $this->config;
    }

    /**
     *
     * @param Config $config 
     */
    public function setConfig(Config $config) {
        $this->config = $config;
    }
}